package closures;

import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.Ordering;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

import static java.util.Arrays.asList;

@SuppressWarnings({"unchecked"})
public class Closures{
    public static void each(Collection collection, String operation){
        new Instance().each(collection, operation);
    }


    public static void runAsync(String block){
        new Thread(runnable(block)).start();
    }

    public static Runnable runnable(String block){
        return new Instance().runnable(block);
    }

    public static Runnable runnable(String block, Object... args){
        return new Instance().runnable(block, args);
    }

    public static <T extends Iterable> T collect(T iterable, String operation){
        return new Instance().collect(iterable, operation);
    }

    public static <T> Iterable<T> transform(Iterable iterable, String operation){ return collect(iterable, operation); }
    public static <T> Iterable<T> map(Iterable iterable, String operation){ return collect(iterable, operation); }

    public static <T extends Iterable> T findAll(T iterable, String operation){
        return new Instance().findAll(iterable, operation);
    }

    public static <T> T find(Iterable<T> iterable, String operation){
        return  new Instance().find(iterable, operation);
    }

    public static Boolean exists(Iterable iterable, String operation){
        return any(iterable, operation);
    }
    public static Boolean any(Iterable iterable, String operation){
        return new Instance().any(iterable, operation);
    }

    public static Boolean every(Iterable iterable, String operation){
        return new Instance().every(iterable, operation);
    }

    public static void each(Iterable iterable, String operation, Object... args){
        new Instance().each(iterable, operation, args);
    }

	public static Function fn(String expression) {
		return new Instance().f(expression);
	}

    public static Function f(String expression) { return fn(expression);}

	public static <T> Supplier<T> fn0(String experssion) {
		return new Instance().f0( experssion);
	}

    public static <T> Supplier<T> f0(String expression) { return fn0(expression);}

    public static <T> Comparator<T> comparator(final String comparator) {
        return new Comparator<T>() {
            GroovyShell shell = new GroovyShell();
            @Override
            public String toString() {
                return "Comparator<"+comparator+">";
            }

            public int compare(Object o1, Object o2) {
                shell.setVariable("arg1", o1);
                shell.setVariable("arg2", o2);
                return (Integer)shell.evaluate(comparator);
            }
        };
    }

    public static <T> T max(String comparatorExpression, Iterable<T> collection) {
        return Ordering.from(comparator(comparatorExpression)).max(collection);
    }

    public static <T> T max(String comparatorExpression, T... collection) {
        return Ordering.from(comparator(comparatorExpression)).max(asList(collection));
    }

    public static <T> T min(String comparatorExpression, Iterable<T> collection) {
        return Ordering.from(comparator(comparatorExpression)).min(collection);
    }
    public static <T> T min(String comparatorExpression, T... collection) {
        return Ordering.from(comparator(comparatorExpression)).min(asList(collection));
    }


    public static class Instance{
        List<String> staticImports = new ArrayList<String>();

        public void each(Collection collection, String operation){
            Binding binding = new Binding();
            binding.setVariable("col", collection);
            GroovyShell shell = new GroovyShell(binding);
            String cmd = "";
            cmd = concatStaticImports(cmd);
            cmd += "col.each {"+ operation +"}";
            shell.evaluate(cmd);
        }

        private String concatStaticImports(String cmd){
            StringBuffer res = new StringBuffer(cmd);
            for(String staticImport: staticImports){
				res.append("import static ").append(staticImport).append(";\n");
            }
            return res.toString();
        }

        public void staticImport(String staticImport){
            staticImports.add(staticImport);
        }

        public Runnable runnable(final String block, final Object... args){
            return new Runnable(){
                public void run(){
                    GroovyShell shell = new GroovyShell(argsBinding(args));
                    shell.evaluate(block);
                }
            };
        }

        private Binding argsBinding(Object... args){
            Binding binding = new Binding();
            for(int i=0;i<args.length; i++) {binding.setVariable("arg"+i, args[i]);}
            return binding;
        }

        public <T extends Iterable> T collect(T collection, String operation){
            return (T)collectionOperation("collect", collection, operation);
        }

        public <T extends Iterable> T findAll(T iterable, String operation){
            return (T) collectionOperation("findAll", iterable, operation);
        }

        private Object collectionOperation(String collectionOperation, Iterable collection, String operation){
            Binding binding = new Binding();
            binding.setVariable("col", collection);
            return new GroovyShell(binding).evaluate("col." + collectionOperation + "{" +operation+"}");
        }

        public <T> T find(Iterable<T> iterable, String operation){
            return (T)collectionOperation("find", iterable, operation);
        }

        public Boolean any(Iterable iterable, String operation){
            return (Boolean)collectionOperation("any", iterable, operation);
        }

        public Boolean every(Iterable iterable, String operation){
            return (Boolean)collectionOperation("every", iterable, operation);
        }

        public void each(Iterable iterable, String operation, Object... args){
            Binding binding = argsBinding(args);
            binding.setVariable("col", iterable);
            GroovyShell shell = new GroovyShell(binding);
            shell.evaluate("col.each{"+operation+"}");
        }

		public <S,T> Function<S,T> f(final String expression) {
			return new Function<S,T>() {
				public T apply(S o) {
					Binding binding = new Binding();
					binding.setVariable("arg", o);
					return (T) new GroovyShell(binding).evaluate(expression);
				}

                @Override
                public String toString() {
                    return "(arg) -> " + expression;
                }
            };
		}

		public <T> Supplier<T> f0(final String experssion) {
			return new Supplier<T>() {
				public T get() {
					return (T) new GroovyShell().evaluate(experssion);
				}

                @Override
                public String toString() {
                    return "() -> " + experssion;
                }
            };
		}
	}
}